package com.globant.navarrete.fabio.utils;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

import org.springframework.transaction.annotation.Transactional;

public abstract class GenericDAOImpl<T> implements IGenericDAO<T> {

  @PersistenceContext
  protected EntityManager em;

  private Class<T> type;

  @SuppressWarnings({ "unchecked", "rawtypes" })
  public GenericDAOImpl() {
    Type t = getClass().getGenericSuperclass();
    ParameterizedType pt = (ParameterizedType) t;
    type = (Class) pt.getActualTypeArguments()[0];
  }

  @SuppressWarnings("unchecked")
  @Override
  public long countAll(@SuppressWarnings("rawtypes") final Map params) {

    final StringBuffer queryString = new StringBuffer("SELECT count(o) from ");

    queryString.append(type.getSimpleName()).append(" o ");
    queryString.append(this.getQueryClauses(params, null));

    final Query query = this.em.createQuery(queryString.toString());

    return (Long) query.getSingleResult();

  }

  @Override
  public T create(final T t) {
    this.em.persist(t);
    return t;
  }

  @Override
  public void delete(final Object id) {
    this.em.remove(this.em.getReference(type, id));
  }

  @Override
  public T find(final Object id) {
    return (T) this.em.find(type, id);
  }

  @Override
  public T update(final T t) {
    return this.em.merge(t);
  }

  private String getQueryClauses(final Map<String, Object> params,
      final Map<String, Object> orderParams) {
    final StringBuffer queryString = new StringBuffer();
    if ((params != null) && !params.isEmpty()) {
      queryString.append(" where ");
      for (final Iterator<Entry<String, Object>> it = params.entrySet().iterator(); it.hasNext();) {
        final Entry<String, Object> entry = it.next();
        if (entry.getValue() instanceof Boolean) {
          queryString.append(entry.getKey()).append(" is ").append(entry.getValue()).append(" ");
        }
        else {
          if (entry.getValue() instanceof Number) {
            queryString.append(entry.getKey()).append(" = ").append(entry.getValue());
          }
          else {
            // string equality
            queryString.append(entry.getKey()).append(" = '").append(entry.getValue()).append("'");
          }
        }
        if (it.hasNext()) {
          queryString.append(" and ");
        }
      }
    }
    if ((orderParams != null) && !orderParams.isEmpty()) {
      queryString.append(" order by ");
      for (final Iterator<Entry<String, Object>> it = orderParams.entrySet().iterator(); it.hasNext();) {
        final Entry<String, Object> entry = it.next();
        queryString.append(entry.getKey()).append(" ");
        if (entry.getValue() != null) {
          queryString.append(entry.getValue());
        }
        if (it.hasNext()) {
          queryString.append(", ");
        }
      }
    }
    return queryString.toString();
  }

  public Collection<T> findAll() {
    String stringQuery = "SELECT o FROM " + this.type.getSimpleName() + " o";
    @SuppressWarnings("unchecked")
    Collection<T> listT = this.em.createQuery(stringQuery).getResultList();
    return listT;
  }
}
